import { dataToArray } from './util';

const scrollId = 'scroll-id';

function EventDispatcher(target) {
  this._listeners = {};
  this._eventTarget = target || {};
  this.recoverLists = [];
  // 给浏览器 的 addEventListener用的
  this._listFun = {};
}
/**
* @namespace
 * namespace属性用于返回触发该事件时所指定的命名空间。
 当需要为同一个元素、同一种事件类型绑定多个事件处理函数时，一般情况下，触发某个事件，
 就会触发执行与之对应的所有事件处理函数；解除某种类型的事件绑定
 ，就会解除该事件类型绑定的所有事件处理函数。
* */
EventDispatcher.prototype = {
  addEventListener(type, callback, target, options) {
    const types = type.split('.');
    const _type = types[0];
    const namespaces = types[1]; // 命名空间
    // 初次
    if (target && !target.getAttribute(scrollId)) {
      // 为target 生成一个为id
      target.setAttribute(
        scrollId,
        (Date.now() + Math.random()).toString(32).replace('.', ''),
      );
    }
    /**
     * @同一个target,listName 是会相同的, 这对于理解代码很重要
     * 无target 时，listName = scroll
     * */
    const listName = `${_type}${
      target ? `_${target.getAttribute(scrollId)}` : ''
    }`;
    let list = this._listeners[listName];
    let index = 0;
    let listener;
    let i;
    // 初次
    if (!list) {
      list = [];
      this._listeners[listName] = list;
    }
    i = list.length;
    // 先删除 namespaces 相同的，并且 callback 相同的
    while (--i > -1) {
      listener = list[i];
      // namespaces 是一个有一个时间戳的，todo 会重复吗？
      if (listener.n === namespaces && listener.c === callback) {
        list.splice(i, 1);
      } else if (index === 0) {
        // 耐人寻味的代码，这个index的目的，不就是在尾部追加一个元素吗
        index = i + 1;
      }
    }
    const $target = target || this._eventTarget;
    // 在末尾添加
    list.splice(index, 0, { c: callback, n: namespaces, t: _type });

    // 初次
    // 同一个 $target， 同一个事件 只需要添加一次 $target.addEventListener,后面只需要调用就好了
    // this._listeners[listName]
    if (!this._listFun[listName]) {
      this._listFun[listName] =
        this._listFun[listName] ||
        this.dispatchEvent.bind(this, { type: _type, target });
      if ($target.addEventListener) {
        $target.addEventListener(_type, this._listFun[listName], options);
      } else if ($target.attachEvent) {
        $target.attachEvent(`on${_type}`, this._listFun[listName]);
      }
    }
  },

  removeEventListener(type, callback, target, force) {
    debugger;
    const types = type.split('.');
    const _type = types[0];
    const namespaces = types[1];
    const listName = `${_type}${
      target ? `_${target.getAttribute(scrollId)}` : ''
    }`;
    const list = this._listeners[listName];
    let i;
    let _force = force;
    if (!namespaces) {
      _force = true;
    }
    if (list) {
      i = list.length;
      const $target = target || this._eventTarget;
      while (--i > -1) {
        if (list[i].c === callback && (_force || list[i].n === namespaces)) {
          list.splice(i, 1);
          if (!list.length) {
            const func = this._listFun[listName];
            delete this._listeners[listName];
            delete this._listFun[listName];
            if ($target.removeEventListener) {
              $target.removeEventListener(_type, func);
            } else if ($target.detachEvent) {
              $target.detachEvent(`on${_type}`, func);
            }
          }
          if (!_force) {
            return;
          }
        }
      }
    }
  },

  /**
   * @触发事件  target元素的 所有 listName 下的事件
   * */
  dispatchEvent({ type, target }, e) {
    const listName = `${type}${
      target ? `_${target.getAttribute(scrollId)}` : ''
    }`;
    const list = this._listeners[listName];
    let i;
    let t;
    let listener;
    if (list) {
      i = list.length;
      t = this._eventTarget;
      while (--i > -1) {
        listener = list[i];
        if (listener) {
          const _e = e || { type, target: t };
          listener.c.call(t, _e);
        }
      }
    }
  },
  removeAllType(type, target) {
    debugger;
    const types = type.split('.');
    const _type = types[0];
    const namespaces = types[1];
    const listName = `${_type}${
      target ? `_${target.getAttribute(scrollId)}` : ''
    }`;
    const list = this._listeners[listName];
    this.recoverLists = this.recoverLists.concat(
      dataToArray(list).filter(item => item.n && item.n.match(namespaces)),
    );
    this.recoverLists.forEach(item => {
      this.removeEventListener(`${item.t}.${item.n}`, item.c, target);
    });
  },
  reAllType(type, target) {
    const types = type.split('.');
    const _type = types[0];
    const namespaces = types[1];
    this.recoverLists = this.recoverLists
      .map(item => {
        if (item.t === _type && item.n.match(namespaces)) {
          this.addEventListener(`${item.t}.${item.n}`, item.c, target);
          return null;
        }
        return item;
      })
      .filter(item => item);
  },
};
export default new EventDispatcher(
  typeof window !== 'undefined' && typeof document !== 'undefined' && window,
);
